//
//  ClosuresExample.swift
//  SwiftCode
//
//  Created by 于虎拴 on 2023/1/9.
//

import Foundation

/// https://gitbook.swiftgg.team/swift/swift-jiao-cheng/07_closures
/// https://swift.gg/2016/09/09/closure-capture-1/
class ClosuresExample: ListViewController {
  override func getItemCount() -> Int {
    data.count
  }
  
  override func getItem(_ indexPath: IndexPath) -> Item {
    data[indexPath.row]
  }
  
  private lazy var data: [Item] = [
    .init(title: "闭包表达式", description: "排序方法的闭包演化", action: testSorted),
    .init(title: "语法", description: "参数 可以是 in-out 参数，但不能设定默认值", action: testSortedBackward),
    .init(title: "值捕获", description: "增量器，闭包为引用类型", action: testMakeIncrementer),
    .init(title: "自动闭包", description: "自动闭包能够延迟求值，调用才会执行", action: autoClosures),
    .init(title: "默认的捕获语义", description: "被捕获的变量的生命周期与闭包自身是一致的", action: captureDefault),
    .init(title: "被捕获的变量（引用类型）在执行时才取值", description: "捕获的是变量的引用", action: captureReference),
    // 面试题
    .init(title: "被捕获的变量（值类型）在执行时才取值", description: "捕获的是变量的引用", action: captureIntReference),
    .init(title: "被捕获的变量重新赋值", description: "捕获的是变量的引用，闭包中修改值", action: resetValue),
    // 面试题
    .init(title: "捕获列表(值类型）", description: "闭包创建时捕获变量", action: captureWhenCreated),
    .init(title: "捕获列表(引用类型）", description: "闭包创建时捕获变量", action: captureReferenceWhenCreated)
  ]
}

private func testSorted() {
  let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
  func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
  }
  var reversedNames = names.sorted(by: backward)
  // reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
  print(reversedNames)
  
  // 闭包表达式语法，内联闭包表达式中，函数和返回值类型都写在大括号内，而不是大括号外。
  reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
  })
  print(reversedNames)
  
  // 根据上下文推断类型
  reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
  print(reversedNames)
  
  // 单表达式闭包的隐式返回
  reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
  print(reversedNames)
  
  // 参数名称缩写
  reversedNames = names.sorted(by: { $0 > $1 } )
  print(reversedNames)
  
  // 运算符方法，Swift 的 String 类型定义了关于大于号（>）的字符串实现
  reversedNames = names.sorted(by: >)
  print(reversedNames)
  
  // 尾随闭包
  reversedNames = names.sorted() { $0 > $1 }
  print(reversedNames)
  // 闭包表达式是函数或方法的唯一参数，则当你使用尾随闭包时，你甚至可以把 () 省略掉
  reversedNames = names.sorted { $0 > $1 }
  print(reversedNames)
}

func doubleInPlace(number: inout Int) {
  number *= 2
}

// 表达式语法
//{ (parameters) -> return type in
//    statements
//}
private func testSortedBackward() {
  // inout 参数
  var count = 4 // 不能将常量整数与inout一起使用
  doubleInPlace(number: &count)
  print(count)
  // 原理
  // 1. 调用函数时，会复制参数的值。
  // 2. 您可以在函数体中修改副本。
  // 3. 当函数返回时，副本的值会重新分配给原始参数。
  // 这种行为被称为 copy-in copy-out 或 call by value result。
  
  let incrementByOne = { number in
    number = number + 1
  }
  incrementByOne(&count)
  print(count)
}

private func makeIncrementer(forIncrement amount: Int) -> () -> Int {
  var runningTotal = 0
  // 嵌套函数，从外围函数捕获了 runningTotal 和 amount 变量的引用。
  func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
  }
  return incrementer
}

/// 创建两个增量器实例，测试闭包为引用类型
private func testMakeIncrementer() {
  let incrementByTen = makeIncrementer(forIncrement: 10)
  var ten = incrementByTen()
  print(ten) // 返回的值为10
  ten = incrementByTen()
  print(ten) // 返回的值为20
  ten = incrementByTen()
  print(ten) // 返回的值为30
  
  let incrementBySeven = makeIncrementer(forIncrement: 7)
  let seven = incrementBySeven()
  // 返回的值为7
  print(seven)
  
  ten = incrementByTen()
  print(ten) // 返回的值为40
  
  // 闭包是引用类型
  let alsoIncrementByTen = incrementByTen
  ten = alsoIncrementByTen()
  print(ten) // 返回的值为50
}

private func autoClosures() {
  var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
  print(customersInLine.count)
  // 打印出“5”
  
  let customerProvider = { customersInLine.remove(at: 0) }
  print(customersInLine.count)
  // 打印出“5”
  
  // 调用闭包
  print("Now serving \(customerProvider())!")
  // 打印出“Now serving Chris!”
  print(customersInLine.count)
  // 打印出“4”
  
  // 自动闭包
  // customersInLine is ["Ewa", "Barry", "Daniella"]
  func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
  }
  serve(customer: customersInLine.remove(at: 0))
  // 打印出“Now serving Alex!”
}

// 定义一个简单的 Pokemon（口袋妖怪）类：
class Pokemon: CustomDebugStringConvertible {
  let name: String
  
  init(name: String) {
    self.name = name
  }
  var debugDescription: String { return "<宝可梦 \(name)>" }
  deinit { print("\(self) 逃逸了!")}
}

// 接下来声明一个简单的函数，它接受一个闭包作为参数，然后在一段时间后执行这个闭包（使用 GCD）
private func delay(seconds: Int, closure: @escaping ()->()) {
  let time = DispatchTime.now() + .seconds(seconds)
  DispatchQueue.main.asyncAfter(deadline: time) {
    print("🕑")
    closure()
  }
}

/// 编译器发现在闭包内部引用了 pokemon 变量，它会自动捕获该变量（默认是强引用），所以 pokemon 的生命周期与闭包自身是一致的。
private func captureDefault() {
  let pokemon = Pokemon(name: "超梦") // 宝可梦：超梦
  print("闭包前: \(pokemon)")
  delay(seconds: 1) {
    print("闭包中: \(pokemon)")
  }
  print("闭包后")
}

/// 捕获的是变量的引用，在闭包执行时才会取出捕获变量的值
/// - 闭包中: <宝可梦 超梦>
private func captureReference() {
  var pokemon = Pokemon(name: "皮卡丘")
  print("闭包前: \(pokemon)")
  delay(seconds: 1) {
    print("闭包中: \(pokemon)")
  }
  pokemon = Pokemon(name: "超梦")
  print("闭包后: \(pokemon)")
}

/// 闭包中: 1337
private func captureIntReference() {
  var value = 42
  print("闭包前: \(value)")
  delay(seconds: 1) {
    print("闭包中: \(value)")
  }
  value = 1337
  print("闭包后: \(value)")
}

/// 闭包(2)中, 值: 1337
private func resetValue() {
  var value = 42
  print("闭包前: \(value)")
  delay(seconds: 1) {
    print("闭包(1)中, 赋值前: \(value)")
    value = 1337
    print("闭包(1)中, 赋值后: \(value)")
  }
  delay(seconds: 2) {
    print("闭包(2)中, 值: \(value)")
  }
}

/// 在闭包创建时捕获变量 value 的内容，而不是变量的引用。
/// - 闭包中 oldValue: 42
/// - 闭包中 newValue: 1337
private func captureWhenCreated() {
  var value = 42
  print("闭包前: \(value)")
  delay(seconds: 1) { [oldValue = value] in
    print("闭包中 oldValue: \(oldValue)")
    print("闭包中 newValue: \(value)")
  }
  value = 1337
  print("闭包后: \(value)")
}

private func captureReferenceWhenCreated() {
  var pokemon = Pokemon(name: "皮卡丘")
  print("闭包前: \(pokemon)")
  delay(seconds: 1) { [pokemonCopy = pokemon] in
    print("闭包中: \(pokemonCopy)")
  }
  pokemon = Pokemon(name: "超梦")
  print("闭包后: \(pokemon)")
}
